<#assign object = doc.object>
package ${object.package}.service;

import java.util.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.logging.Level;
import java.util.logging.Logger;
import redora.rdo.api.*;
import redora.rdo.api.BusinessRuleViolation.Action;
import redora.rdo.exceptions.*;
import redora.rdo.impl.*;
import ${object.package}.model.*;
import ${object.package}.model.fields.*;
<#if object.attributes.xml[0]??>
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import ${object.package}.model.xml.*;
import javax.xml.bind.JAXBException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
</#if>
<#list doc["/object/attributes/enum[@scope='global']"] as att>
import ${object.package}.model.enums.${att.@class};
</#list>
<#if object.trashcan[0]?? && object.trashcan == "true">
import redora.rdo.configuration.rdo.service.TrashService;
</#if>
/**
* You can work with the pojo's using this service. For example if you create command line J2SE functionality
* or server side logic.<br>
* If you want to use your data in a web application, you also might want to look at {@link ${object.name}ServiceJSON}
* and {@link ${object.package}.service.http.${object.name}Servlet}.<br>
* Whenever business rule logic needs to be checked, it will go through here. For example a web user wants to persist
* some data. He will post this for example as JSON to {@link ${object.package}.service.http.${object.name}Servlet}
* which in turn invokes {@link ${object.name}ServiceJSON} which will serialize the JSON stream into a pojo
* and then will invoke the persist method of this service.
* @author persistence
*/
public class ${object.name}Service extends ServiceBase {
	private Logger l = Logger.getLogger("${object.package}.service.${object.name}Service");
	
	<#list object.attributes.xml as att>
	private JAXBContext ${att.@name}_ctx = null;
	private Marshaller ${att.@name}_m = null;
	</#list>
	/**
	* Don't use this directly but use the ServiceFactory instead: <br />
	* ${object.name}Service() myService = ServiceFactory.${object.name?uncap_first}();
	* <br />
	* Initializes ${object.name}Service and connects to the database.
	* The connection will be cached until you issue close(). 
	* @throws ConnectException When a connection cannot be established.
	* @see ${object.package}.service.ServiceFactory
	*/
	public ${object.name}Service() throws ConnectException {
		super();
	<#list object.attributes.xml as att>
		try {
			${att.@name}_ctx = JAXBContext.newInstance(
				new Class[] {${att.@class[0]!att.@name?cap_first}.class});
			${att.@name}_m = ${att.@name}_ctx.createMarshaller();
		} catch (Exception e) {
			throw new ConnectException("failed to bind ${att.@class[0]!att.@name?cap_first}!", e);
		}
	</#list>
	}

	/** @return Null, or the desired ${object.name} */
	public ${object.name} findById(Long id) throws ConnectException, QueryException, CopyException {
		${object.name} retVal = null;
		try {
			makePreparedStatement(${object.name}SQL.FIND_BY_ID);
			ps.setLong(1, id.longValue());
			rs = ps.executeQuery();
			if (rs.next())
				retVal = ${object.name}Util.copy(rs, 0);
			rs.close();
			rs = null;
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_BY_ID, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_BY_ID, e);
	  	}
		return retVal;
	}

  /**
  * Finds records by custom SQL.
  * @param params (Optional) List of parameters. When omitted it is assumed there are no parameters.
  */
  public List<${object.name}> find(String SQL, List<Object> params) throws ConnectException, QueryException, CopyException {
    List<${object.name}> retVal = new ArrayList<${object.name}>();
    try {
      makePreparedStatement(SQL);
      int position = 1;
      if (params != null) {
        for (Object param: params) {
          if (param instanceof Integer) {
            ps.setInt(position++, (Integer)param);
          } else if (param instanceof Long) {
            ps.setLong(position++, (Long)param);
          } else if (param instanceof String) {
            ps.setString(position++, (String)param);
          }
        }
      }

      rs = ps.executeQuery();
      while (rs.next()) {
        retVal.add(${object.name}Util.copy(rs, 0));
      }
      rs.close();
      rs = null;
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to run query: " + SQL, e);
      throw new QueryException("Failed to run query: " + SQL, e);
    }
    
    return retVal;
  }

	public List<${object.name}> findAll() throws ConnectException, QueryException, CopyException {
		List<${object.name}> retVal = new ArrayList<${object.name}>();
		try {
			makePreparedStatement(${object.name}SQL.FIND_ALL);
			rs = ps.executeQuery();
			while (rs.next())
				retVal.add(${object.name}Util.copy(rs, 0));
			rs.close();
			rs = null;
	  	} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_ALL, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_ALL, e);
	  	}
		return retVal;
	}
	
<#list doc["/object/attributes/set[@multiplicity='n-to-m']"] as att>
  /**
  * Finds <@plural att=att /> based on their (n-to-m) relation with ${att.@class}.
  */
  public List<${object.name}> findBy${att.@class}Id (Long id) throws ConnectException, CopyException, QueryException {
    List<${object.name}> retVal = new ArrayList<${object.name}>();
    try {
      makePreparedStatement(${object.name}SQL.FIND_${object.name?upper_case}_BY_${att.@class?upper_case}ID);
      ps.setLong(1, id);
      rs = ps.executeQuery();
      while (rs.next())
        retVal.add(${object.name}Util.copy(rs, 0));
      rs.close();
      rs = null;
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_${object.name?upper_case}_BY_${att.@class?upper_case}ID, e);
      throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_${object.name?upper_case}_BY_${att.@class?upper_case}ID, e);
    }
    return retVal;
  }

</#list>
<#list object.attributes?children as att>
    <#if (att?node_name == "long" && att.@name?ends_with("Id")) || att?node_name == "object">
        <@relation att=att />
  public List<${object.name}> findBy${relation_name?cap_first}(Long id) throws ConnectException, QueryException, CopyException {
    List<${object.name}> retVal = new ArrayList<${object.name}>();
    try {
      makePreparedStatement(${object.name}SQL.FIND_BY_${relation_name?upper_case});
      ps.setLong(1, id.longValue());
      rs = ps.executeQuery();
      while (rs.next())
        retVal.add(${object.name}Util.copy(rs, 0));
      rs.close();
      rs = null;
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_BY_${relation_name?upper_case}, e);
      throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_BY_${relation_name?upper_case}, e);
    }
    
    return retVal;
  }

  /** Deletes all ${object.name}s without checking on business rules. This is usually invoked by the related parent which has already checked business rule violations. */
  public void deleteBy${relation_name?cap_first}(Long id) throws ConnectException, QueryException {
    try {
      makePreparedStatement(${object.name}SQL.DELETE_BY_${relation_name?upper_case});
      ps.setLong(1, id.longValue());
      ps.execute();
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to run delete query " + ${object.name}SQL.DELETE_BY_${relation_name?upper_case}, e);
      throw new QueryException("Failed to run query: " + ${object.name}SQL.DELETE_BY_${relation_name?upper_case}, e);
    }
  }

        <#if object.trashcan[0]?? && object.trashcan == "true">
  /** Trashes all ${object.name}s without checking on business rules. This is usually invoked by the related parent which has already checked business rule violations. */
  public void trashBy${relation_name?cap_first}(Long id, boolean undo) throws ConnectException, QueryException {
    try {
      makePreparedStatement(${object.name}SQL.TRASH_BY_${relation_name?upper_case});
      ps.setBoolean(1, !undo);
      ps.setLong(2, id.longValue());
      ps.execute();
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to run delete query " + ${object.name}SQL.TRASH_BY_${relation_name?upper_case}, e);
      throw new QueryException("Failed to run query: " + ${object.name}SQL.TRASH_BY_${relation_name?upper_case}, e);
    }
  }
        </#if>
    </#if>
</#list>

<#list object.attributes.enum as att>
	public List<${object.name}> findBy${att.@class}(<#if !att.@scope[0]?? || att.@scope == "local">${object.name}.</#if>${att.@class} ${att.@class?uncap_first}) throws ConnectException, QueryException, CopyException {
		List<${object.name}> retVal = new ArrayList<${object.name}>();
		try {
			makePreparedStatement(${object.name}SQL.FIND_BY_${att.@class?upper_case});
			ps.setString(1, ${att.@class?uncap_first}.name());
			rs = ps.executeQuery();
			while (rs.next())
				retVal.add(${object.name}Util.copy(rs, 0));
			rs.close();
			rs = null;
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_BY_${att.@class?upper_case}, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_BY_${att.@class?upper_case}, e);
	  	}
		return retVal;
	}
</#list>

<#list object.attributes.xml as att>
	public ${att.@class[0]!att.@name?cap_first} find${att.@name?cap_first}ById(Long id) throws ConnectException, QueryException, CopyException {
		${att.@class[0]!att.@name?cap_first} retVal = new ObjectFactory().create${att.@class[0]!att.@name?cap_first}();
		try {
			makePreparedStatement(${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID);
			ps.setLong(1, id);
			rs = ps.executeQuery();
			if (rs.next()) {
				${att.@class[0]!att.@name?cap_first} ${att.@name}_doc = null;
				InputStream in = null;
				in = rs.getBinaryStream(1);
				try {
					JAXBContext ${att.@name}_ctx = JAXBContext.newInstance(
							new Class[] {${att.@class[0]!att.@name?cap_first}.class});
					Unmarshaller ${att.@name}_um = ${att.@name}_ctx.createUnmarshaller();
					if (in != null) {
						${att.@name}_doc = (${att.@class[0]!att.@name?cap_first}) ${att.@name}_um.unmarshal(in);
					} else {
						${att.@name}_doc = null;
					}
				} catch (JAXBException e) {
					throw new CopyException("Failed to bind ${att.@name}!", e);
				}
				if (${att.@name}_doc != null) {
					retVal = ${att.@name}_doc;
				}
			}
			rs.close();
			rs = null;
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID, e);
	  	}
		return retVal;
	}
</#list>

<#list doc["/object/attributes/*[@finder='true']"] as att>
	public List<${object.name}> findBy${att.@name?cap_first}(${att?node_name?cap_first} finder) throws ConnectException, QueryException, CopyException {
		List<${object.name}> retVal = new ArrayList<${object.name}>();
		try {
			makePreparedStatement(${object.name}SQL.FIND_BY_${att.@name?upper_case});
			<#if att?node_name == "long">
			ps.setLong(1, finder.longValue());
			<#elseif att?node_name == "integer">
			ps.setInt(1, finder.intValue());
			<#elseif att?node_name == "string">
			ps.setString(1, finder);
			<#elseif att?node_name == "enum">
			ps.setString(1, finder.name());
			<#elseif att?node_name == "date">
			ps.setDate(1, new java.sql.Date(finder.getTime()));
			</#if>
			rs = ps.executeQuery();
			while (rs.next())
				retVal.add(${object.name}Util.copy(rs, 0));
			rs.close();
			rs = null;
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_BY_${att.@name?upper_case}, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_BY_${att.@name?upper_case}, e);
	  	}
		return retVal;
	}
</#list>

<#list doc["/object/attributes/*[@lazy='true']"] as att>
	<#if att?node_name == "string">
	public String find${att.@name?cap_first}ById (Long id) throws ConnectException, QueryException {
		String retVal = null;
		try {
			makePreparedStatement(${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID);
			ps.setLong(1,id);
			rs = ps.executeQuery();
			if (rs.next())
				retVal = rs.getString(1);
			rs.close();
			rs = null;
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to run query " + ${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID, e);
			throw new QueryException("Failed to run query: " + ${object.name}SQL.FIND_${att.@name?upper_case}_BY_ID, e);
	  	}
		return retVal;
	}
	</#if>
</#list>

	/**
	* Inserts or updates ${object.name}. Collections associated to ${object.name} are persisted as well.
	**/
	public Set<BusinessRuleViolation> persist(${object.name} pojo) throws ConnectException, PersistException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException, LazyException</#if> {
		if (!evaluateChange(pojo))
			return new HashSet<BusinessRuleViolation>();
		Set<BusinessRuleViolation> br = checkBR(pojo, pojo.isNew ? Action.Insert : Action.Update);
		if (br.size() != 0)
			return br;
		persist(pojo, false);
		
		return new HashSet<BusinessRuleViolation>();
	}

	/**
	* Inserts or updates a collection of ${object.name}. 
	* Collections associated to ${object.name} are persisted as well.
	* JDBC batch update is used to improve performance.
	**/
	public Set<BusinessRuleViolation> persist(List<${object.name}> pojos) throws ConnectException, PersistException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException, LazyException</#if> {
		boolean unChanged = true;
		for (${object.name} pojo : pojos)
			if (evaluateChange(pojo))
				unChanged = false;
		if (unChanged)
			return new HashSet<BusinessRuleViolation>();

		Set<BusinessRuleViolation> br = new HashSet<BusinessRuleViolation>();
		for (${object.name} pojo : pojos) {
			br.addAll(checkBR(pojo, pojo.isNew ? Action.Insert : Action.Update));
		}
		
		if (br.size() != 0)
			return br;

		for (${object.name} pojo : pojos)
			persist(pojo, true);

		try {
			ps.executeBatch();
			ResultSet _r = ps.getGeneratedKeys(); 
			while (_r.next())
				for (${object.name} pojo : pojos)
					if (pojo.getId() == null)
						pojo.id = _r.getLong(1);
			_r.close();
		} catch(SQLException e) {
			l.log(Level.SEVERE, "Failed to perform batch update, i was trying to update a list of " + pojos.size() + " ${object.name}s", e);
			throw new PersistException("Failed to perform batch update, i was trying to update a list of " + pojos.size() + " ${object.name}s", e);		
		}
		return br;
	}
	
  protected Set<BusinessRuleViolation> checkBR(${object.name} pojo, Action when) throws ConnectException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException</#if> {
    Set<BusinessRuleViolation> retVal = new HashSet<BusinessRuleViolation>();
    
    ${object.name}BusinessRules br = new ${object.name}BusinessRules(); 
    if (when == Action.Delete) {
      retVal.addAll(br.onDelete(pojo));
    } else if (when == Action.Insert) {
      retVal.addAll(br.onInsert(pojo));
    } else {
      retVal.addAll(br.onUpdate(pojo));
    }

<#list object.attributes.set as att>
    //Operations on ${att.@multiplicity[0]!"1-to-n"} relation with ${att.@class}:
    ${att.@class}BusinessRules br${att.@class} = new ${att.@class}BusinessRules();
    for (${att.@class} child : pojo.get<@plural att=att />()) {
      if (when == Action.Delete) {
    <#if !att.@cascadeDelete[0]?? || att.@cascadeDelete == "true">
        retVal.addAll(br${att.@class}.onDelete(child));
    <#else>
        l.log(Level.FINE, "Ignoring business rule check because cascade delete is turned of");
    </#if>
      } else {
    <#if !att.@multiplicity[0]?? || att.@multiplicity == "1-to-n">
        if (when == Action.Insert) {
          child.set${object.name}Id(-1L); //make sure the not-null constraint is not violated, it will be reset on persist()
        } else {
          child.set${object.name}Id(pojo.getId());
        }
    </#if>
        if (child.isNew) {
          retVal.addAll(br${att.@class}.onInsert(child));
        } else {
          retVal.addAll(br${att.@class}.onUpdate(child));
        }
      }
    }
    <#if (!att.@multiplicity[0]?? || att.@multiplicity == "1-to-n") && (!att.@cascadeDelete[0]?? || att.@cascadeDelete == "true")>
    for (${att.@class} delete : pojo.get<@plural att=att />().getRemovedObjects())
      retVal.addAll(br${att.@class}.onDelete(delete));
    </#if>
</#list>
		
    return retVal;
  }

	/**
	* @return true when ${object.name} or any related children are changed
	*/
	protected static boolean evaluateChange(${object.name} pojo) <#if object.attributes.set[0]??>throws ConnectException, QueryException, CopyException</#if> {
		if (pojo.isDirty())
			return true;
		<#list object.attributes.set as att>
		if (pojo.get<@plural att=att />().isDirty())
			return true;
		</#list>
		return false;
	}
	
<#list doc["/object/attributes/set[@multiplicity='n-to-m']"] as att>
	<@relation_table att=att />
  /**
  * Removes the relationship with ${att.@class} by deleting a record in the ${relation_table_name} table. 
  */
  protected void deleteRelationWith${att.@class}(Long ${object.name?uncap_first}Id, Long ${att.@class?uncap_first}Id, boolean asBatch)
      throws ConnectException, PersistException {
    try {
      makePreparedStatement(${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION);
      ps.setLong(1, ${object.name?uncap_first}Id);
      ps.setLong(2, ${att.@class?uncap_first}Id);
      if(!asBatch) {
        ps.execute();
      } else {
        ps.addBatch();
      }
    } catch (SQLException e) {
      l.log(Level.SEVERE, "Failed to perform delete: "
        + ${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION, e);
      throw new PersistException("Failed to perform delete: "
        + ${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION, e);
    }
  }
    
  /**
  * Removes the all relationships with ${att.@class} by deleting the records in the ${relation_table_name} table. 
  */
  protected void deleteAllRelationsWith${att.@class}(Long ${object.name?uncap_first}Id, boolean asBatch)
      throws ConnectException, PersistException {
    try {
      makePreparedStatement(${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID);
      ps.setLong(1, ${object.name?uncap_first}Id);
      if (!asBatch) {
        ps.execute();
      } else {
        ps.addBatch();
      }
    } catch (SQLException e) {
      l.log(Level.SEVERE, "Failed to perform delete: "
        + ${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID, e);
      throw new PersistException("Failed to perform delete: "
        + ${object.name}SQL.DELETE_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID, e);
    }
  }

    <#if object.trashcan[0]?? && object.trashcan == "true">
  /**
  * Moves the relationship with ${att.@class} to the trash can. 
  */
  protected void trashRelationWith${att.@class}(Long ${object.name?uncap_first}Id, Long ${att.@class?uncap_first}Id, boolean asBatch, boolean undo)
      throws ConnectException, PersistException {
    try {
      makePreparedStatement(${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION);
      ps.setBoolean(1, !undo);
      ps.setLong(2, ${object.name?uncap_first}Id);
      ps.setLong(3, ${att.@class?uncap_first}Id);
      if(!asBatch) {
        ps.execute();
      } else {
        ps.addBatch();
      }
    } catch (SQLException e) {
      l.log(Level.SEVERE, "Failed to perform trash: "
        + ${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION, e);
      throw new PersistException("Failed to perform trash: "
        + ${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION, e);
    }
  }  

  /**
  * Moves all relationships with ${att.@class} to the trash can. 
  */
  protected void trashAllRelationsWith${att.@class}(Long ${object.name?uncap_first}Id, boolean asBatch, boolean undo)
      throws ConnectException, PersistException {
    try {
      makePreparedStatement(${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID);
      ps.setBoolean(1, !undo);
      ps.setLong(2, ${object.name?uncap_first}Id);
      if (!asBatch) {
        ps.execute();
      } else {
        ps.addBatch();
      }
    } catch (SQLException e) {
      l.log(Level.SEVERE, "Failed to perform trash: "
        + ${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID, e);
      throw new PersistException("Failed to perform trash: "
        + ${object.name}SQL.TRASH_${relation_table_name?upper_case}_RELATION_BY_${object.name?upper_case}ID, e);
    }
  }
    </#if>

  /**
  * Creates a relationship with {att.@class} by creating a record in the ${relation_table_name} table. 
  */
  protected void connectTo${att.@class}(Long ${object.name?uncap_first}Id, Long ${att.@class?uncap_first}Id, boolean asBatch)
      throws ConnectException, QueryException, CopyException, PersistException {
    try {
    //TODO this is wrong, the check if a connection exist must first be done in the PersistableSet and secend done with a select query
    
      deleteRelationWith${att.@class}(${object.name?uncap_first}Id, ${att.@class?uncap_first}Id, asBatch);
      makePreparedStatement(${object.name}SQL.INSERT_${relation_table_name?upper_case}_RELATION);
      ps.setLong(1, ${object.name?uncap_first}Id);
      ps.setLong(2, ${att.@class?uncap_first}Id);
      if(!asBatch){
        ps.execute();
      } else {
        ps.addBatch();
      }
    } catch (SQLException e) {
      l.log(Level.SEVERE, "Failed to perform insert: "
        + ${object.name}SQL.INSERT_${relation_table_name?upper_case}_RELATION, e);
      throw new PersistException("Failed to perform insert: "
        + ${object.name}SQL.INSERT_${relation_table_name?upper_case}_RELATION, e);
    }
  }
</#list>

  /**
  * Raw persist function. Persists the changes of ${object.name} to the database and
  * also any changed related children.
  * Business rules are not checked, that is why this method is private.
  * @param asBatch Set to tru if you want to execute the statements outside this method. You need to invoke ps.executeBatch() yourself.	
  */
  protected void persist(${object.name} pojo, boolean asBatch) throws ConnectException, PersistException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException</#if> {
    l.fine("Updating " + pojo.getId());

    String SQL = null;
    try {
      if (pojo.dirty.size() != 0) {
        if (pojo.isNew) {
          pojo.dirty.add(${object.name}Fields.creationDate);
          pojo.creationDate = new Date((new Date().getTime()/1000)*1000); //Strip nanoseconds, they are not persisted in MySQL
          StringBuilder sqlInsert = new StringBuilder("insert into ${object.name} (");
          char comma = ' ';
          for (${object.name}Fields field : pojo.dirty) {
            sqlInsert.append(comma);
            sqlInsert.append(field.name());
            comma = ',';
          }
          sqlInsert.append(") values (");
          comma = ' ';
          for (int i = 0; i < pojo.dirty.size(); i++) {
            sqlInsert.append(comma);
            sqlInsert.append('?');
            comma = ',';
          }
          sqlInsert.append(')');
          SQL = sqlInsert.toString();
        } else {
          if (pojo.dirty.size() > 0) {
            pojo.dirty.add(${object.name}Fields.updateDate);
            pojo.updateDate = new Date((new Date().getTime()/1000)*1000); //Strip nanoseconds, they are not persisted in MySQL
            StringBuilder sqlUpdate = new StringBuilder("update ${object.name} set ");
            char comma = ' ';
            for (${object.name}Fields field : pojo.dirty) {
              sqlUpdate.append(comma);
              sqlUpdate.append(field.name()).append("=?");
              comma = ',';
            }
            sqlUpdate.append(" where id=?");
            SQL = sqlUpdate.toString();
          }
        }
        
        makePreparedStatement(SQL);
        
        int position = 1;
        for (${object.name}Fields field : pojo.dirty) {
          switch (field) {
            case updateDate:
              ps.setTimestamp(position++, new Timestamp(pojo.updateDate.getTime()));
              break;
            case creationDate:
              ps.setTimestamp(position++, new Timestamp(pojo.creationDate.getTime()));
              break;
    <#list object.attributes?children as att>
        <#if att?node_name != "set" && att?node_type == "element">
            case ${att.@name[0]!att.@class?uncap_first}<#if att?node_name="object">Id</#if>:
            <#if !att.@notnull[0]?? || att.@notnull == "false"> 
              if (pojo.get${att.@class[0]!att.@name?cap_first}() == null) ps.setNull(position++, field.sqlType);
              else
            </#if>
            <#if att?node_name="boolean">
                ps.setBoolean(position++, pojo.get${att.@name?cap_first}().booleanValue());
            <#elseif att?node_name == "date" || att?node_name == "datetime">
                ps.setDate(position++, new java.sql.Date(pojo.get${att.@name?cap_first}().getTime()));
            <#elseif att?node_name == "enum">
                ps.setString(position++, pojo.get${att.@class}().name());
            <#elseif att?node_name == "integer">
                ps.setInt(position++, pojo.get${att.@name?cap_first}().intValue());
            <#elseif att?node_name == "long">
                ps.setLong(position++, pojo.get${att.@name?cap_first}().longValue());
            <#elseif att?node_name == "object">
                ps.setLong(position++, pojo.get${att.@class}Id().longValue());
            <#elseif att?node_name == "string" || att?node_name == "html">
                <#if att.@maxlength[0]?number &gt; 65000 || att?node_name == "html">
                ps.setBinaryStream(position++, org.apache.commons.io.IOUtils.toInputStream(pojo.get${att.@name?cap_first}()));
                <#else>
                ps.setString(position++, pojo.get${att.@name?cap_first}());
                </#if>
            <#elseif att?node_name == "xml"> 
             {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ${att.@name}_m.marshal(pojo.get${att.@name?cap_first}(), out);
                InputStream in = new ByteArrayInputStream(out.toByteArray());
                ps.setBinaryStream(position++, in);
             }
            <#else>
            ERROR undefined type ${att?node_name}
            </#if>
              break;
      </#if>
    </#list>
          }
        }
        
        if (!pojo.isNew)
          ps.setLong(position++, pojo.id.longValue());
          
        if (!asBatch)
          ps.execute();
        else
          ps.addBatch();
        
        if (pojo.isNew && !asBatch)
          pojo.id = ps.getLastInsertID();
        
        pojo.clean();
      }
    
    <#list object.attributes.set as att>
        <#if att.@multiplicity[0]?? && att.@multiplicity[0] == "n-to-m">
      for (${att.@class} delete : pojo.get<@plural att=att />().getRemovedObjects()) {
        deleteRelationWith${att.@class}(pojo.getId(), delete.getId(), asBatch);
      }
      for (${att.@class} list : pojo.get<@plural att=att />()) {
        if (list.isNew || list.isDirty()) {
          ServiceFactory.${att.@class?uncap_first}Service().persist(list, asBatch);
        }
        connectTo${att.@class}(pojo.getId(), list.getId(), asBatch);
      }
        <#else>
      if (pojo.get<@plural att=att />().getRemovedObjects().size() > 0) {
        ${att.@class}Service childService = ServiceFactory.${att.@class?uncap_first}Service();
        for (${att.@class} delete : pojo.get<@plural att=att />().getRemovedObjects())
          childService.delete(delete);
      }
      List<${att.@class}> update<@plural att=att /> = new LinkedList<${att.@class}>();
      for (${att.@class} child : pojo.get<@plural att=att />()) {
        if (child.isDirty()) {
          update<@plural att=att />.add(child);
          child.set${object.name}Id(pojo.getId());
        }
      }
      if (!update<@plural att=att />.isEmpty()) {
        ServiceFactory.${att.@class?uncap_first}Service().persist(update<@plural att=att />);
        for (${att.@class} child : update<@plural att=att />)
          pojo.get<@plural att=att />().setMapKey(child);
      }
        </#if>
      //TODO: re check this, it might be wrong...
      if (!pojo.get<@plural att=att />().isRetrieved()) pojo.get<@plural att=att />().clear();
    </#list>
      if (pojo.isNew) pojo.isNew = false;
    } catch(Exception e) {
      l.log(Level.SEVERE, "Failed to perform update: " + SQL, e);
      throw new PersistException("Failed to perform update: " + SQL, e);
    }
  }

  /**
  * Deletes ${object.name}. 
  * Collections associated to ${object.name} are deleted as well when cascade delete is indicated.
  **/
  public Set<BusinessRuleViolation> delete(${object.name} pojo) throws ConnectException, PersistException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException</#if> {
    Set<BusinessRuleViolation> br = checkBR(pojo, Action.Delete);
    if (br.size() == 0) {
      delete(pojo, false);
    }
    
    return br;
  }

  /** Deletes ${object.name} without checking on business rule violations. */
  private void delete(${object.name} pojo, boolean asBatch) throws ConnectException, PersistException {
    l.fine("Deleting " + pojo.getId());
    
    try {
<#list object.attributes.set as att>
    <#if att.@multiplicity[0]?? && att.@multiplicity == "n-to-m">
      if (!pojo.get<@plural att=att />().isEmpty()) {
        deleteAllRelationsWith${att.@class}(pojo.getId(), asBatch);
      }
    <#elseif !att.@cascadeDelete[0]?? || att.@cascadeDelete == "true">
      if (!pojo.get<@plural att=att />().isEmpty()) {
        ServiceFactory.${att.@class?uncap_first}Service().deleteBy${object.name}Id(pojo.getId());
      }
    </#if>
</#list>
      makePreparedStatement(${object.name}SQL.DELETE);
      ps.setLong(1, pojo.getId().longValue());
      if (!asBatch)
        ps.execute();
      else
        ps.addBatch();
    } catch(Exception e) {
      l.log(Level.SEVERE, "Failed to perform delete: " + ${object.name}SQL.DELETE, e);
      throw new PersistException("Failed to perform delete: " + ${object.name}SQL.DELETE, e);
    }
  }

<#if object.trashcan[0]?? && object.trashcan == "true">
  /**
  * Finds trashed ${object.name}s. 
  * @param ${object.name?uncap_first}Id (Optional) if set only a specific trahed object is retrieved.
  **/
  public List<${object.name}> findTrash(Long ${object.name?uncap_first}Id) throws ConnectException, QueryException, CopyException {
  	List<${object.name}> retVal = new ArrayList<${object.name}>();
    String SQL = null;
    if (${object.name?uncap_first}Id == null) {
      SQL = ${object.name}SQL.FIND_TRASH;
    } else {
      SQL = ${object.name}SQL.FIND_TRASH_BY_ID;
    }
    
    try {
      makePreparedStatement(SQL);
      if (${object.name?uncap_first}Id != null) {
        ps.setLong(1, ${object.name?uncap_first}Id);
      }
      rs = ps.executeQuery();
      while (rs.next()) {
        retVal.add(${object.name}Util.copy(rs, 0));
      }
      rs.close();
      rs = null;
    } catch(SQLException e) {
      l.log(Level.SEVERE, "Failed to find trash: " + SQL, e);
      throw new QueryException("Failed to find trash: " + SQL, e);
    }
    
    return retVal;
  }

  /**
  * Trashes ${object.name}. 
  * Collections associated to ${object.name} are trashed as well when cascade delete is indicated.
  **/
  public Set<BusinessRuleViolation> trash(String undoHash, ${object.name} pojo) throws ConnectException, QueryException, PersistException, BusinessRuleViolationException<#if object.attributes.set[0]??>, QueryException, CopyException</#if> {
    Set<BusinessRuleViolation> br = checkBR(pojo, Action.Delete);
    if (br.size() == 0) {
      TrashService.trash(undoHash, pojo.getId(), "${object.name}");
      trash(pojo, false, false);
    }
    
    return br;
  }

  /**
  * Restored a trashed ${object.name} and also restored possible trashed children.
  * @throws QueryException When ${object.name} was not found (or was untrashed before).
  */
  public void undo(Long ${object.name?uncap_first}Id) throws ConnectException, QueryException, PersistException, BusinessRuleViolationException, CopyException {
    List<${object.name}> trash = findTrash(${object.name?uncap_first}Id);
    
    if (trash.isEmpty()) {
      throw new QueryException("Could not find trashed ${object.name}, maybe it was already undeleted.");
    }
    
    ${object.name} pojo = trash.iterator().next();
    trash(pojo, false, true);
    TrashService.deleteTrash(pojo.getId(), "${object.name}");
  }

  /** Trashes ${object.name} without checking on business rule violations. */
  private void trash(${object.name} pojo, boolean asBatch, boolean undo)
      throws ConnectException, QueryException, PersistException {
    l.fine("Trashing " + pojo.getId());
    
    try {
    <#list object.attributes.set as att>
        <#if att.@multiplicity[0]?? && att.@multiplicity == "n-to-m">
      if (!pojo.get<@plural att=att />().isEmpty()) {
        trashAllRelationsWith${att.@class}(pojo.getId(), asBatch, undo);
      }
        <#elseif !att.@cascadeDelete[0]?? || att.@cascadeDelete == "true">
      if (!pojo.get<@plural att=att />().isEmpty() || undo) {
        ServiceFactory.${att.@class?uncap_first}Service().trashBy${object.name}Id(pojo.getId(), undo);
      }
        </#if>
    </#list>
      makePreparedStatement(${object.name}SQL.TRASH);
      ps.setBoolean(1, !undo);
      ps.setLong(2, pojo.getId().longValue());
      if (!asBatch)
        ps.execute();
      else
        ps.addBatch();
    } catch(Exception e) {
      l.log(Level.SEVERE, "Failed to perform trash: " + ${object.name}SQL.TRASH, e);
      throw new PersistException("Failed to perform trash: " + ${object.name}SQL.TRASH, e);
    }
  }
</#if>
	
	/** Test - dumps findAll */
	public static void main(String[] args) throws ConnectException, QueryException, CopyException {
		for (${object.name} testRecord : ServiceFactory.${object.name?uncap_first}Service().find(${object.name}SQL.TEST, null)) {
		<#list object.attributes?children as att>
		  <#if att?node_name != "set" && att?node_name != "object" && att?node_type == "element">
			if (testRecord.${att.@name[0]!att.@class?uncap_first} != null)
				System.out.print(testRecord.${att.@name[0]!att.@class?uncap_first}.toString());
			else
				System.out.print("NULL");
			System.out.print("-");
		  </#if>
		</#list>
		System.out.println();
		}
	}
}
